﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using JigLibX.Collision;
using JigLibX.Physics;
using JigLibX.Geometry;
using JigLibX.Math;

namespace DARE
{
    /// <summary>
    /// Create a simple collision box from a model.
    /// Complex models with multiple mesh parts will be inside the created bounding box, 
    /// but it's not working yet :D Only models with one mesh part will work.
    /// CModel should be given in arg, because with AModel we can't create an approx mesh box for a CBasicModel
    /// </summary>
    public class CPhysicMeshApprox : APhysicObject
    {

    #region fields

        private Vector3 m_center;
        private Vector3 m_size;
        private CModel m_model;

    #endregion
        
    #region properties

        public Vector3 Center
        {
            get { return m_center; }
        }

        public Vector3 Size
        {
            get { return m_size; }
        }

    #endregion

    #region ctor

        public CPhysicMeshApprox()
        {
            Initialize(null);
        }

        public CPhysicMeshApprox(CModel model)
        {
            Initialize(model);
        }

        private void Initialize(CModel model)
        {
            if (model == null)
                throw new Exception("Cannot create a physic mesh appriximation without model !");
            m_model = model;
            m_center = new Vector3();
            m_size = new Vector3();

            GenerateBoundingBox(model, out m_center, out m_size);
            CreateBoundingBox(m_center, m_size);

            // offset the model, this should be the physic instead
            m_model.Offset = -m_center;

            //Console.WriteLine("Model node: " + m_model.Node.Name + 
                //"\nnode position = " + m_model.Node.Position.ToString() + 
                //"\ncenter = " + m_center.ToString());
        }

    #endregion

    #region methods

        static void GenerateBoundingBox(CModel model, out Vector3 center, out Vector3 size)
        {
            Vector3 minX = new Vector3();
            Vector3 maxX = new Vector3();
            Vector3 minY = new Vector3();
            Vector3 maxY = new Vector3();
            Vector3 minZ = new Vector3();
            Vector3 maxZ = new Vector3();

            ExtractExtremePoints(model, out minX, out maxX, out minY, out maxY, out minZ, out maxZ);
            size = new Vector3(maxX.X - minX.X, maxY.Y - minY.Y, maxZ.Z - minZ.Z);
            center = new Vector3((minX.X + maxX.X) / 2.0f, (minY.Y + maxY.Y) / 2.0f, (minZ.Z + maxZ.Z) / 2.0f);

            //Console.WriteLine(
                //"Extremes points:" + 
                //"\nminX = " + minX.ToString() + 
                //"\nmaxX = " + maxX.ToString() + 
                //"\nminY = " + minY.ToString() + 
                //"\nmaxY = " + maxY.ToString() + 
                //"\nminZ = " + minZ.ToString() + 
                //"\nmaxZ = " + maxZ.ToString() + 
                //"\nProperties: " + 
                //"\nsize = " + size.ToString() + 
                //"\ncenter = " + center.ToString()
                //);
        }

        static void ExtractExtremePoints(CModel model, out Vector3 minX, out Vector3 maxX, 
            out Vector3 minY, out Vector3 maxY, out Vector3 minZ, out Vector3 maxZ)
        {
            // assign inverse float extremes values to compare the point with the current vertice
            minX = Vector3.UnitX * float.MaxValue;
            maxX = Vector3.UnitX * float.MinValue;
            minY = Vector3.UnitY * float.MaxValue;
            maxY = Vector3.UnitY * float.MinValue;
            minZ = Vector3.UnitZ * float.MaxValue;
            maxZ = Vector3.UnitZ * float.MinValue;
            // work only with one meshpart because the parent bone transform matrix is not taken under considaration
            // iterate throught all our points
            foreach (ModelMesh mm in model.Model.Meshes)
                foreach (ModelMeshPart mmp in mm.MeshParts)
                {
                    VertexPositionNormalTexture[] vpnt = new VertexPositionNormalTexture[mmp.VertexBuffer.VertexCount];
                    mmp.VertexBuffer.GetData<VertexPositionNormalTexture>(vpnt);
                    // search the extremes
                    for (int i = 0; i < vpnt.Length; ++i)
                    {
                        Vector3 point = vpnt[i].Position * model.Scale;
                        if (point.X < minX.X) minX = point;
                        if (point.X > maxX.X) maxX = point;
                        if (point.Y < minY.Y) minY = point;
                        if (point.Y > maxY.Y) maxY = point;
                        if (point.Z < minZ.Z) minZ = point;
                        if (point.Z > maxZ.Z) maxZ = point;
                    }

                    //Console.WriteLine("============= Effect parameters ==============");
                    //foreach (EffectParameter ep in mmp.Effect.Parameters)
                    //{
                    //    Console.WriteLine(ep.Name);

                    //    Console.WriteLine("===== Elements ====");
                    //    //foreach (EffectParameter op1 in ep.Elements)
                    //    //{
                        
                    //        Console.WriteLine(ep.ParameterClass + " : " + ep.ParameterType);
                    //    //}
                    //}
                }
        }

        private void CreateBoundingBox(Vector3 center, Vector3 size)
        {
            m_body = new Body();
            m_collisionSkin = new CollisionSkin(m_body);
            m_body.CollisionSkin = m_collisionSkin;

            Box box = new Box(Vector3.Zero, Matrix.Identity, size);
            m_collisionSkin.AddPrimitive(box, new MaterialProperties(0.8f, 0.8f, 0.7f));

            Vector3 com = SetMass(1.0f);
            m_collisionSkin.ApplyLocalTransform(new Transform(-com, Matrix.Identity));
            m_body.EnableBody();

            m_basicModel = new CBoxModel(size.X, size.Y, size.Z);
        }

    #endregion

    }
}
